// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/event_constants.h"
#include "ui/events/gesture_detection/gesture_event_data.h"
#include "ui/events/gesture_detection/gesture_provider.h"
#include "ui/events/gesture_detection/motion_event.h"
#include "ui/events/test/motion_event_test_utils.h"
#include "ui/gfx/geometry/point_f.h"

using base::TimeDelta;
using base::TimeTicks;
using ui::test::MockMotionEvent;

namespace ui {
namespace {

    const float kFakeCoordX = 42.f;
    const float kFakeCoordY = 24.f;
    const TimeDelta kOneSecond = TimeDelta::FromSeconds(1);
    const TimeDelta kOneMicrosecond = TimeDelta::FromMicroseconds(1);
    const TimeDelta kDeltaTimeForFlingSequences = TimeDelta::FromMilliseconds(5);
    const float kMockTouchRadius = MockMotionEvent::TOUCH_MAJOR / 2;
    const float kMaxTwoFingerTapSeparation = 300;

    GestureProvider::Config CreateDefaultConfig()
    {
        GestureProvider::Config sConfig;
        // The longpress timeout is non-zero only to indicate ordering with respect to
        // the showpress timeout.
        sConfig.gesture_detector_config.showpress_timeout = base::TimeDelta();
        sConfig.gesture_detector_config.longpress_timeout = kOneMicrosecond;

        // A valid doubletap timeout should always be non-zero. The value is used not
        // only to trigger the timeout that confirms the tap event, but also to gate
        // whether the second tap is in fact a double-tap (using a strict inequality
        // between times for the first up and the second down events). We use 4
        // microseconds simply to allow several intermediate events to occur before
        // the second tap at microsecond intervals.
        sConfig.gesture_detector_config.double_tap_timeout = kOneMicrosecond * 4;
        sConfig.gesture_detector_config.double_tap_min_time = kOneMicrosecond * 2;
        return sConfig;
    }

    gfx::RectF BoundsForSingleMockTouchAtLocation(float x, float y)
    {
        float diameter = MockMotionEvent::TOUCH_MAJOR;
        return gfx::RectF(x - diameter / 2, y - diameter / 2, diameter, diameter);
    }

} // namespace

class GestureProviderTest : public testing::Test, public GestureProviderClient {
public:
    GestureProviderTest() { }
    ~GestureProviderTest() override { }

    static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time,
        MotionEvent::Action action,
        float x,
        float y)
    {
        return MockMotionEvent(action, event_time, x, y);
    }

    static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time,
        MotionEvent::Action action,
        float x0,
        float y0,
        float x1,
        float y1)
    {
        return MockMotionEvent(action, event_time, x0, y0, x1, y1);
    }

    static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time,
        MotionEvent::Action action,
        float x0,
        float y0,
        float x1,
        float y1,
        float x2,
        float y2)
    {
        return MockMotionEvent(action, event_time, x0, y0, x1, y1, x2, y2);
    }

    static MockMotionEvent ObtainMotionEvent(
        base::TimeTicks event_time,
        MotionEvent::Action action,
        const std::vector<gfx::PointF>& positions)
    {
        switch (positions.size()) {
        case 1:
            return MockMotionEvent(
                action, event_time, positions[0].x(), positions[0].y());
        case 2:
            return MockMotionEvent(action,
                event_time,
                positions[0].x(),
                positions[0].y(),
                positions[1].x(),
                positions[1].y());
        case 3:
            return MockMotionEvent(action,
                event_time,
                positions[0].x(),
                positions[0].y(),
                positions[1].x(),
                positions[1].y(),
                positions[2].x(),
                positions[2].y());
        default:
            CHECK(false) << "MockMotionEvent only supports 1-3 pointers";
            return MockMotionEvent();
        }
    }

    static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time,
        MotionEvent::Action action)
    {
        return ObtainMotionEvent(event_time, action, kFakeCoordX, kFakeCoordY);
    }

    // Test
    void SetUp() override { SetUpWithConfig(GetDefaultConfig()); }

    void TearDown() override
    {
        gestures_.clear();
        gesture_provider_.reset();
    }

    // GestureProviderClient
    void OnGestureEvent(const GestureEventData& gesture) override
    {
        if (gesture.type() == ET_GESTURE_SCROLL_BEGIN)
            active_scroll_begin_event_.reset(new GestureEventData(gesture));
        gestures_.push_back(gesture);
    }

    void SetUpWithConfig(const GestureProvider::Config& config)
    {
        gesture_provider_.reset(new GestureProvider(config, this));
        gesture_provider_->SetMultiTouchZoomSupportEnabled(false);
    }

    void ResetGestureDetection()
    {
        gesture_provider_->ResetDetection();
        gestures_.clear();
    }

    bool CancelActiveTouchSequence()
    {
        if (!gesture_provider_->current_down_event())
            return false;
        return gesture_provider_->OnTouchEvent(
            *gesture_provider_->current_down_event()->Cancel());
    }

    bool HasReceivedGesture(EventType type) const
    {
        for (size_t i = 0; i < gestures_.size(); ++i) {
            if (gestures_[i].type() == type)
                return true;
        }
        return false;
    }

    const GestureEventData& GetMostRecentGestureEvent() const
    {
        EXPECT_FALSE(gestures_.empty());
        return gestures_.back();
    }

    EventType GetMostRecentGestureEventType() const
    {
        EXPECT_FALSE(gestures_.empty());
        return gestures_.back().type();
    }

    size_t GetReceivedGestureCount() const { return gestures_.size(); }

    const GestureEventData& GetReceivedGesture(size_t index) const
    {
        EXPECT_LT(index, GetReceivedGestureCount());
        return gestures_[index];
    }

    const GestureEventData* GetActiveScrollBeginEvent() const
    {
        return active_scroll_begin_event_ ? active_scroll_begin_event_.get() : NULL;
    }

    const GestureProvider::Config& GetDefaultConfig() const
    {
        static GestureProvider::Config sConfig = CreateDefaultConfig();
        return sConfig;
    }

    float GetTouchSlop() const
    {
        return GetDefaultConfig().gesture_detector_config.touch_slop;
    }

    float GetMinScalingSpan() const
    {
        return GetDefaultConfig().scale_gesture_detector_config.min_scaling_span;
    }

    float GetMinSwipeVelocity() const
    {
        return GetDefaultConfig().gesture_detector_config.minimum_swipe_velocity;
    }

    base::TimeDelta GetLongPressTimeout() const
    {
        return GetDefaultConfig().gesture_detector_config.longpress_timeout;
    }

    base::TimeDelta GetShowPressTimeout() const
    {
        return GetDefaultConfig().gesture_detector_config.showpress_timeout;
    }

    base::TimeDelta GetDoubleTapTimeout() const
    {
        return GetDefaultConfig().gesture_detector_config.double_tap_timeout;
    }

    base::TimeDelta GetDoubleTapMinTime() const
    {
        return GetDefaultConfig().gesture_detector_config.double_tap_min_time;
    }

    base::TimeDelta GetValidDoubleTapDelay() const
    {
        return (GetDoubleTapTimeout() + GetDoubleTapMinTime()) / 2;
    }

    void EnableBeginEndTypes()
    {
        GestureProvider::Config config = GetDefaultConfig();
        config.gesture_begin_end_types_enabled = true;
        SetUpWithConfig(config);
    }

    void EnableSwipe()
    {
        GestureProvider::Config config = GetDefaultConfig();
        config.gesture_detector_config.swipe_enabled = true;
        SetUpWithConfig(config);
    }

    void EnableTwoFingerTap(float max_distance_for_two_finger_tap,
        base::TimeDelta two_finger_tap_timeout)
    {
        GestureProvider::Config config = GetDefaultConfig();
        config.gesture_detector_config.two_finger_tap_enabled = true;
        config.gesture_detector_config.two_finger_tap_max_separation = max_distance_for_two_finger_tap;
        config.gesture_detector_config.two_finger_tap_timeout = two_finger_tap_timeout;
        SetUpWithConfig(config);
    }

    void SetMinPinchUpdateSpanDelta(float min_pinch_update_span_delta)
    {
        GestureProvider::Config config = GetDefaultConfig();
        config.scale_gesture_detector_config.min_pinch_update_span_delta = min_pinch_update_span_delta;
        SetUpWithConfig(config);
    }

    void SetMinMaxGestureBoundsLength(float min_gesture_bound_length,
        float max_gesture_bound_length)
    {
        GestureProvider::Config config = GetDefaultConfig();
        config.min_gesture_bounds_length = min_gesture_bound_length;
        config.max_gesture_bounds_length = max_gesture_bound_length;
        SetUpWithConfig(config);
    }

    void SetShowPressAndLongPressTimeout(base::TimeDelta showpress_timeout,
        base::TimeDelta longpress_timeout)
    {
        GestureProvider::Config config = GetDefaultConfig();
        config.gesture_detector_config.showpress_timeout = showpress_timeout;
        config.gesture_detector_config.longpress_timeout = longpress_timeout;
        SetUpWithConfig(config);
    }

    void SetSingleTapRepeatInterval(int repeat_interval)
    {
        GestureProvider::Config config = GetDefaultConfig();
        config.gesture_detector_config.single_tap_repeat_interval = repeat_interval;
        SetUpWithConfig(config);
    }

    bool HasDownEvent() const { return gesture_provider_->current_down_event(); }

protected:
    void CheckScrollEventSequenceForEndActionType(
        MotionEvent::Action end_action_type)
    {
        base::TimeTicks event_time = base::TimeTicks::Now();
        const float scroll_to_x = kFakeCoordX + 100;
        const float scroll_to_y = kFakeCoordY + 100;
        int motion_event_id = 3;
        int motion_event_flags = EF_SHIFT_DOWN | EF_CAPS_LOCK_ON;

        MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
        event.SetPrimaryPointerId(motion_event_id);
        event.set_flags(motion_event_flags);

        EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
        EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);

        event = ObtainMotionEvent(event_time + kOneSecond,
            MotionEvent::ACTION_MOVE,
            scroll_to_x,
            scroll_to_y);
        event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
        event.SetPrimaryPointerId(motion_event_id);
        event.set_flags(motion_event_flags);

        EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
        EXPECT_TRUE(gesture_provider_->IsScrollInProgress());
        EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
        EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
        EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
        EXPECT_EQ(event.GetToolType(0),
            GetMostRecentGestureEvent().primary_tool_type);
        EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
        EXPECT_EQ(BoundsForSingleMockTouchAtLocation(scroll_to_x, scroll_to_y),
            GetMostRecentGestureEvent().details.bounding_box_f());
        ASSERT_EQ(3U, GetReceivedGestureCount()) << "Only TapDown, "
                                                    "ScrollBegin and ScrollBy "
                                                    "should have been sent";

        EXPECT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type());
        EXPECT_EQ(motion_event_id, GetReceivedGesture(1).motion_event_id);
        EXPECT_EQ(event_time + kOneSecond, GetReceivedGesture(1).time)
            << "ScrollBegin should have the time of the ACTION_MOVE";

        event = ObtainMotionEvent(
            event_time + kOneSecond, end_action_type, scroll_to_x, scroll_to_y);
        event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
        event.SetPrimaryPointerId(motion_event_id);

        gesture_provider_->OnTouchEvent(event);
        EXPECT_FALSE(gesture_provider_->IsScrollInProgress());
        EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_END));
        EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
        EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
        EXPECT_EQ(event.GetToolType(0),
            GetMostRecentGestureEvent().primary_tool_type);
        EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
        EXPECT_EQ(BoundsForSingleMockTouchAtLocation(scroll_to_x, scroll_to_y),
            GetMostRecentGestureEvent().details.bounding_box_f());
    }

    void OneFingerSwipe(float vx, float vy)
    {
        std::vector<gfx::Vector2dF> velocities;
        velocities.push_back(gfx::Vector2dF(vx, vy));
        MultiFingerSwipe(velocities);
    }

    void TwoFingerSwipe(float vx0, float vy0, float vx1, float vy1)
    {
        std::vector<gfx::Vector2dF> velocities;
        velocities.push_back(gfx::Vector2dF(vx0, vy0));
        velocities.push_back(gfx::Vector2dF(vx1, vy1));
        MultiFingerSwipe(velocities);
    }

    void ThreeFingerSwipe(float vx0,
        float vy0,
        float vx1,
        float vy1,
        float vx2,
        float vy2)
    {
        std::vector<gfx::Vector2dF> velocities;
        velocities.push_back(gfx::Vector2dF(vx0, vy0));
        velocities.push_back(gfx::Vector2dF(vx1, vy1));
        velocities.push_back(gfx::Vector2dF(vx2, vy2));
        MultiFingerSwipe(velocities);
    }

    void MultiFingerSwipe(std::vector<gfx::Vector2dF> velocities)
    {
        ASSERT_GT(velocities.size(), 0U);

        base::TimeTicks event_time = base::TimeTicks::Now();

        std::vector<gfx::PointF> positions(velocities.size());
        for (size_t i = 0; i < positions.size(); ++i)
            positions[i] = gfx::PointF(kFakeCoordX * (i + 1), kFakeCoordY * (i + 1));

        float dt = kDeltaTimeForFlingSequences.InSecondsF();

        // Each pointer down should be a separate event.
        for (size_t i = 0; i < positions.size(); ++i) {
            const size_t pointer_count = i + 1;
            std::vector<gfx::PointF> event_positions(pointer_count);
            event_positions.assign(positions.begin(),
                positions.begin() + pointer_count);
            MockMotionEvent event = ObtainMotionEvent(event_time,
                pointer_count > 1 ? MotionEvent::ACTION_POINTER_DOWN
                                  : MotionEvent::ACTION_DOWN,
                event_positions);
            EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
        }

        for (size_t i = 0; i < positions.size(); ++i)
            positions[i] += gfx::ScaleVector2d(velocities[i], dt);
        MockMotionEvent event = ObtainMotionEvent(event_time + kDeltaTimeForFlingSequences,
            MotionEvent::ACTION_MOVE,
            positions);
        EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

        for (size_t i = 0; i < positions.size(); ++i)
            positions[i] += gfx::ScaleVector2d(velocities[i], dt);
        event = ObtainMotionEvent(event_time + 2 * kDeltaTimeForFlingSequences,
            MotionEvent::ACTION_MOVE,
            positions);
        EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

        event = ObtainMotionEvent(event_time + 2 * kDeltaTimeForFlingSequences,
            MotionEvent::ACTION_POINTER_UP,
            positions);
        EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    }

    static void RunTasksAndWait(base::TimeDelta delay)
    {
        base::MessageLoop::current()->PostDelayedTask(
            FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), delay);
        base::MessageLoop::current()->Run();
    }

    std::vector<GestureEventData> gestures_;
    scoped_ptr<GestureProvider> gesture_provider_;
    scoped_ptr<GestureEventData> active_scroll_begin_event_;
    base::MessageLoopForUI message_loop_;
};

// Verify that a DOWN followed shortly by an UP will trigger a single tap.
TEST_F(GestureProviderTest, GestureTap)
{
    base::TimeTicks event_time = base::TimeTicks::Now();
    int motion_event_id = 6;
    int motion_event_flags = EF_CONTROL_DOWN | EF_ALT_DOWN;

    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
    event.SetPrimaryPointerId(motion_event_id);
    event.set_flags(motion_event_flags);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(event.GetToolType(0),
        GetMostRecentGestureEvent().primary_tool_type);
    EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP);
    event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
    event.SetPrimaryPointerId(motion_event_id);
    event.set_flags(motion_event_flags);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    // Ensure tap details have been set.
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());
    EXPECT_EQ(event.GetToolType(0),
        GetMostRecentGestureEvent().primary_tool_type);
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
        GetMostRecentGestureEvent().details.bounding_box_f());
}

// Verify that a DOWN followed shortly by an UP will trigger
// a ET_GESTURE_TAP_UNCONFIRMED event if double-tap is enabled.
TEST_F(GestureProviderTest, GestureTapWithDelay)
{
    base::TimeTicks event_time = base::TimeTicks::Now();
    int motion_event_id = 6;
    int motion_event_flags = EF_CONTROL_DOWN | EF_ALT_DOWN | EF_CAPS_LOCK_ON;

    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    event.SetPrimaryPointerId(motion_event_id);
    event.set_flags(motion_event_flags);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP);
    event.SetPrimaryPointerId(motion_event_id);
    event.set_flags(motion_event_flags);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
    // Ensure tap details have been set.
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
        GetMostRecentGestureEvent().details.bounding_box_f());
    EXPECT_EQ(event.GetEventTime(), GetMostRecentGestureEvent().time);

    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_TAP));
    RunTasksAndWait(GetDoubleTapTimeout());
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_TAP));
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(event.GetEventTime(), GetMostRecentGestureEvent().time);
}

// Verify that a DOWN followed by a MOVE will trigger fling (but not LONG).
TEST_F(GestureProviderTest, GestureFlingAndCancelLongPress)
{
    base::TimeTicks event_time = TimeTicks::Now();
    base::TimeDelta delta_time = kDeltaTimeForFlingSequences;
    int motion_event_id = 6;
    int motion_event_flags = EF_ALT_DOWN;

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    event.SetPrimaryPointerId(motion_event_id);
    event.set_flags(motion_event_flags);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(event_time + delta_time,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX * 10,
        kFakeCoordY * 10);
    event.SetPrimaryPointerId(motion_event_id);
    event.set_flags(motion_event_flags);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time + delta_time * 2,
        MotionEvent::ACTION_UP,
        kFakeCoordX * 10,
        kFakeCoordY * 10);
    event.SetPrimaryPointerId(motion_event_id);
    event.set_flags(motion_event_flags);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_SCROLL_FLING_START, GetMostRecentGestureEventType());
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_PRESS));
    EXPECT_EQ(
        BoundsForSingleMockTouchAtLocation(kFakeCoordX * 10, kFakeCoordY * 10),
        GetMostRecentGestureEvent().details.bounding_box_f());
}

// Verify that for a normal scroll the following events are sent:
// - ET_GESTURE_SCROLL_BEGIN
// - ET_GESTURE_SCROLL_UPDATE
// - ET_GESTURE_SCROLL_END
TEST_F(GestureProviderTest, ScrollEventActionUpSequence)
{
    CheckScrollEventSequenceForEndActionType(MotionEvent::ACTION_UP);
}

// Verify that for a cancelled scroll the following events are sent:
// - ET_GESTURE_SCROLL_BEGIN
// - ET_GESTURE_SCROLL_UPDATE
// - ET_GESTURE_SCROLL_END
TEST_F(GestureProviderTest, ScrollEventActionCancelSequence)
{
    CheckScrollEventSequenceForEndActionType(MotionEvent::ACTION_CANCEL);
}

// Verify that for a normal fling (fling after scroll) the following events are
// sent:
// - ET_GESTURE_SCROLL_BEGIN
// - ET_SCROLL_FLING_START
TEST_F(GestureProviderTest, FlingEventSequence)
{
    base::TimeTicks event_time = base::TimeTicks::Now();
    base::TimeDelta delta_time = kDeltaTimeForFlingSequences;
    int motion_event_id = 6;

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    event.SetPrimaryPointerId(motion_event_id);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time + delta_time,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX * 5,
        kFakeCoordY * 5);
    event.SetPrimaryPointerId(motion_event_id);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(gesture_provider_->IsScrollInProgress());
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    ASSERT_EQ(3U, GetReceivedGestureCount());
    ASSERT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type());
    EXPECT_EQ(motion_event_id, GetReceivedGesture(1).motion_event_id);

    // We don't want to take a dependency here on exactly how hints are calculated
    // for a fling (eg. may depend on velocity), so just validate the direction.
    int hint_x = GetReceivedGesture(1).details.scroll_x_hint();
    int hint_y = GetReceivedGesture(1).details.scroll_y_hint();
    EXPECT_TRUE(hint_x > 0 && hint_y > 0 && hint_x > hint_y)
        << "ScrollBegin hint should be in positive X axis";

    event = ObtainMotionEvent(event_time + delta_time * 2,
        MotionEvent::ACTION_UP,
        kFakeCoordX * 10,
        kFakeCoordY * 10);
    event.SetPrimaryPointerId(motion_event_id);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(gesture_provider_->IsScrollInProgress());
    EXPECT_EQ(ET_SCROLL_FLING_START, GetMostRecentGestureEventType());
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_END));
    EXPECT_EQ(event_time + delta_time * 2, GetMostRecentGestureEvent().time)
        << "FlingStart should have the time of the ACTION_UP";
}

TEST_F(GestureProviderTest, GestureCancelledOnCancelEvent)
{
    const base::TimeTicks event_time = TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());

    RunTasksAndWait(GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SHOW_PRESS));
    EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType());

    // A cancellation event may be triggered for a number of reasons, e.g.,
    // from a context-menu-triggering long press resulting in loss of focus.
    EXPECT_TRUE(CancelActiveTouchSequence());
    EXPECT_FALSE(HasDownEvent());

    // A final ACTION_UP should have no effect.
    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_UP);
    EXPECT_FALSE(gesture_provider_->OnTouchEvent(event));
}

TEST_F(GestureProviderTest, GestureCancelledOnDetectionReset)
{
    const base::TimeTicks event_time = TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());

    RunTasksAndWait(GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SHOW_PRESS));
    EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType());

    ResetGestureDetection();
    EXPECT_FALSE(HasDownEvent());

    // A final ACTION_UP should have no effect.
    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_UP);
    EXPECT_FALSE(gesture_provider_->OnTouchEvent(event));
}

TEST_F(GestureProviderTest, NoTapAfterScrollBegins)
{
    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX + 50,
        kFakeCoordY + 50);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());

    event = ObtainMotionEvent(event_time + kOneSecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX + 50,
        kFakeCoordY + 50);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP));
}

TEST_F(GestureProviderTest, DoubleTap)
{
    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_DOWN,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    // Moving a very small amount of distance should not trigger the double tap
    // drag zoom mode.
    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 1);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY + 1);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    const GestureEventData& double_tap = GetMostRecentGestureEvent();
    EXPECT_EQ(ET_GESTURE_DOUBLE_TAP, double_tap.type());
    // Ensure tap details have been set.
    EXPECT_EQ(10, double_tap.details.bounding_box().width());
    EXPECT_EQ(10, double_tap.details.bounding_box().height());
    EXPECT_EQ(1, double_tap.details.tap_count());
}

TEST_F(GestureProviderTest, DoubleTapDragZoomBasic)
{
    const base::TimeTicks down_time_1 = TimeTicks::Now();
    const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay();

    MockMotionEvent event = ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(down_time_1 + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(
        down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 100);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
    ASSERT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY + 100),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 200);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    ASSERT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType());
    EXPECT_LT(1.f, GetMostRecentGestureEvent().details.scale());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY + 200),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 100);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    ASSERT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType());
    EXPECT_GT(1.f, GetMostRecentGestureEvent().details.scale());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY + 100),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 4,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY - 200);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_END));
    EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY - 200),
        GetMostRecentGestureEvent().details.bounding_box_f());
}

// Generate a scroll gesture and verify that the resulting scroll motion event
// has both absolute and relative position information.
TEST_F(GestureProviderTest, ScrollUpdateValues)
{
    const float delta_x = 16;
    const float delta_y = 84;
    const float raw_offset_x = 17.3f;
    const float raw_offset_y = 13.7f;

    const base::TimeTicks event_time = TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    // Move twice so that we get two ET_GESTURE_SCROLL_UPDATE events and can
    // compare the relative and absolute coordinates.
    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX - delta_x / 2,
        kFakeCoordY - delta_y / 2);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX - delta_x,
        kFakeCoordY - delta_y);
    event.SetRawOffset(raw_offset_x, raw_offset_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    // Make sure the reported gesture event has all the expected details.
    ASSERT_LT(0U, GetReceivedGestureCount());
    GestureEventData gesture = GetMostRecentGestureEvent();
    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, gesture.type());
    EXPECT_EQ(event_time + kOneMicrosecond * 2, gesture.time);
    EXPECT_EQ(kFakeCoordX - delta_x, gesture.x);
    EXPECT_EQ(kFakeCoordY - delta_y, gesture.y);
    EXPECT_EQ(kFakeCoordX - delta_x + raw_offset_x, gesture.raw_x);
    EXPECT_EQ(kFakeCoordY - delta_y + raw_offset_y, gesture.raw_y);
    EXPECT_EQ(1, gesture.details.touch_points());

    // No horizontal delta because of snapping.
    EXPECT_EQ(0, gesture.details.scroll_x());
    EXPECT_EQ(-delta_y / 2, gesture.details.scroll_y());
}

// Verify that fractional scroll deltas are rounded as expected and that
// fractional scrolling doesn't break scroll snapping.
TEST_F(GestureProviderTest, FractionalScroll)
{
    const float delta_x = 0.4f;
    const float delta_y = 5.2f;

    const base::TimeTicks event_time = TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    // Skip past the touch slop and move back.
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 100);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    // Now move up slowly, mostly vertically but with a (fractional) bit of
    // horizontal motion.
    for (int i = 1; i <= 10; i++) {
        event = ObtainMotionEvent(event_time + kOneMicrosecond * i,
            MotionEvent::ACTION_MOVE,
            kFakeCoordX + delta_x * i,
            kFakeCoordY + delta_y * i);
        EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

        ASSERT_LT(0U, GetReceivedGestureCount());
        GestureEventData gesture = GetMostRecentGestureEvent();
        EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, gesture.type());
        EXPECT_EQ(event_time + kOneMicrosecond * i, gesture.time);
        EXPECT_EQ(1, gesture.details.touch_points());

        // Verify that the event co-ordinates are still the precise values we
        // supplied.
        EXPECT_EQ(kFakeCoordX + delta_x * i, gesture.x);
        EXPECT_FLOAT_EQ(kFakeCoordY + delta_y * i, gesture.y);

        // Verify that we're scrolling vertically by the expected amount
        // (modulo rounding).
        EXPECT_GE(gesture.details.scroll_y(), (int)delta_y);
        EXPECT_LE(gesture.details.scroll_y(), ((int)delta_y) + 1);

        // And that there has been no horizontal motion at all.
        EXPECT_EQ(0, gesture.details.scroll_x());
    }
}

// Generate a scroll gesture and verify that the resulting scroll begin event
// has the expected hint values.
TEST_F(GestureProviderTest, ScrollBeginValues)
{
    const float delta_x = 13;
    const float delta_y = 89;

    const base::TimeTicks event_time = TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    // Move twice such that the first event isn't sufficient to start
    // scrolling on it's own.
    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX + 2,
        kFakeCoordY + 1);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(gesture_provider_->IsScrollInProgress());

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX + delta_x,
        kFakeCoordY + delta_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(gesture_provider_->IsScrollInProgress());

    const GestureEventData* scroll_begin_gesture = GetActiveScrollBeginEvent();
    ASSERT_TRUE(scroll_begin_gesture);
    EXPECT_EQ(delta_x, scroll_begin_gesture->details.scroll_x_hint());
    EXPECT_EQ(delta_y, scroll_begin_gesture->details.scroll_y_hint());
}

TEST_F(GestureProviderTest, LongPressAndTapCancelledWhenScrollBegins)
{
    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX * 5,
        kFakeCoordY * 5);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX * 10,
        kFakeCoordY * 10);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    const base::TimeDelta long_press_timeout = GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
    RunTasksAndWait(long_press_timeout);

    // No LONG_TAP as the LONG_PRESS timer is cancelled.
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_PRESS));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP));
}

// Verify that LONG_TAP is triggered after LONG_PRESS followed by an UP.
TEST_F(GestureProviderTest, GestureLongTap)
{
    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    const base::TimeDelta long_press_timeout = GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
    RunTasksAndWait(long_press_timeout);

    EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(event_time + kOneSecond, MotionEvent::ACTION_UP);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(ET_GESTURE_LONG_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
        GetMostRecentGestureEvent().details.bounding_box_f());
}

TEST_F(GestureProviderTest, GestureLongPressDoesNotPreventScrolling)
{
    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    const base::TimeDelta long_press_timeout = GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
    RunTasksAndWait(long_press_timeout);

    EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    event = ObtainMotionEvent(event_time + long_press_timeout,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX + 100,
        kFakeCoordY + 100);
    gesture_provider_->OnTouchEvent(event);

    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));

    event = ObtainMotionEvent(event_time + long_press_timeout,
        MotionEvent::ACTION_UP);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP));
}

TEST_F(GestureProviderTest, NoGestureLongPressDuringDoubleTap)
{
    base::TimeTicks event_time = base::TimeTicks::Now();
    int motion_event_id = 6;

    MockMotionEvent event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_DOWN,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_TRUE(gesture_provider_->IsDoubleTapInProgress());

    const base::TimeDelta long_press_timeout = GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
    RunTasksAndWait(long_press_timeout);
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_PRESS));

    event = ObtainMotionEvent(event_time + long_press_timeout,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX + 20,
        kFakeCoordY + 20);
    event.SetPrimaryPointerId(motion_event_id);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType());
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_TRUE(gesture_provider_->IsDoubleTapInProgress());

    event = ObtainMotionEvent(event_time + long_press_timeout + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY + 1);
    event.SetPrimaryPointerId(motion_event_id);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_FALSE(gesture_provider_->IsDoubleTapInProgress());
}

// Verify that the touch slop region is removed from the first scroll delta to
// avoid a jump when starting to scroll.
TEST_F(GestureProviderTest, TouchSlopRemovedFromScroll)
{
    const float touch_slop = GetTouchSlop();
    const float scroll_delta = 5;

    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + touch_slop + scroll_delta);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
    GestureEventData gesture = GetMostRecentGestureEvent();
    EXPECT_EQ(0, gesture.details.scroll_x());
    EXPECT_EQ(scroll_delta, gesture.details.scroll_y());
    EXPECT_EQ(1, gesture.details.touch_points());
}

// Verify that movement within the touch slop region does not generate a scroll,
// and that the slop region is correct even when using fractional coordinates.
TEST_F(GestureProviderTest, NoScrollWithinTouchSlop)
{
    const float touch_slop = GetTouchSlop();
    const float scale_factor = 2.5f;
    const int touch_slop_pixels = static_cast<int>(scale_factor * touch_slop);

    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX + touch_slop_pixels / scale_factor,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + touch_slop_pixels / scale_factor);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX - touch_slop_pixels / scale_factor,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY - touch_slop_pixels / scale_factor);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + (touch_slop_pixels + 1.f) / scale_factor);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
}

TEST_F(GestureProviderTest, NoDoubleTapWhenTooRapid)
{
    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    // If the second tap follows the first in too short a time span, no double-tap
    // will occur.
    event_time += (GetDoubleTapMinTime() / 2);
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_DOWN,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
}

TEST_F(GestureProviderTest, NoDoubleTapWhenExplicitlyDisabled)
{
    // Ensure that double-tap gestures can be disabled.
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);

    base::TimeTicks event_time = base::TimeTicks::Now();
    MockMotionEvent event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());

    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_DOWN,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());

    // Ensure that double-tap gestures can be interrupted.
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);

    event_time = base::TimeTicks::Now();
    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(5U, GetReceivedGestureCount());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());

    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());

    // Ensure that double-tap gestures can be resumed.
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);

    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_DOWN,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());

    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_DOWN,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_DOUBLE_TAP, GetMostRecentGestureEventType());
}

TEST_F(GestureProviderTest, NoDelayedTapWhenDoubleTapSupportToggled)
{
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);

    base::TimeTicks event_time = base::TimeTicks::Now();
    MockMotionEvent event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1U, GetReceivedGestureCount());

    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
    EXPECT_EQ(2U, GetReceivedGestureCount());

    // Disabling double-tap during the tap timeout should flush the delayed tap.
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(3U, GetReceivedGestureCount());

    // No further timeout gestures should arrive.
    const base::TimeDelta long_press_timeout = GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
    RunTasksAndWait(long_press_timeout);
    EXPECT_EQ(3U, GetReceivedGestureCount());
}

TEST_F(GestureProviderTest, NoDoubleTapDragZoomWhenDisabledOnPlatform)
{
    const base::TimeTicks down_time_1 = TimeTicks::Now();
    const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay();

    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);

    MockMotionEvent event = ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(down_time_1 + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    gesture_provider_->OnTouchEvent(event);

    event = ObtainMotionEvent(
        down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 100);

    // The move should become a scroll, as doubletap drag zoom is disabled.
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 200);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(down_time_2 + kOneMicrosecond * 2,
        GetMostRecentGestureEvent().time);
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY + 200);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_END));
}

// Verify that double tap drag zoom feature is not invoked when the gesture
// handler is told to disable double tap gesture detection.
// The second tap sequence should be treated just as the first would be.
TEST_F(GestureProviderTest, NoDoubleTapDragZoomWhenDisabledOnPage)
{
    const base::TimeTicks down_time_1 = TimeTicks::Now();
    const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay();

    gesture_provider_->SetDoubleTapSupportForPageEnabled(false);

    MockMotionEvent event = ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(down_time_1 + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    gesture_provider_->OnTouchEvent(event);

    event = ObtainMotionEvent(
        down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 100);

    // The move should become a scroll, as double tap drag zoom is disabled.
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 200);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY + 200);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_END));
}

// Verify that updating double tap support during a double tap drag zoom
// disables double tap detection after the gesture has ended.
TEST_F(GestureProviderTest, FixedPageScaleDuringDoubleTapDragZoom)
{
    base::TimeTicks down_time_1 = TimeTicks::Now();
    base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay();

    gesture_provider_->SetDoubleTapSupportForPageEnabled(true);
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);

    // Start a double-tap drag gesture.
    MockMotionEvent event = ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(down_time_1 + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    gesture_provider_->OnTouchEvent(event);
    event = ObtainMotionEvent(
        down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 100);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
    EXPECT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    // Simulate setting a fixed page scale (or a mobile viewport);
    // this should not disrupt the current double-tap gesture.
    gesture_provider_->SetDoubleTapSupportForPageEnabled(false);

    // Double tap zoom updates should continue.
    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 200);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_LT(1.f, GetMostRecentGestureEvent().details.scale());
    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY + 200);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_END));
    EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    // The double-tap gesture has finished, but the page scale is fixed.
    // The same event sequence should not generate any double tap getsures.
    gestures_.clear();
    down_time_1 += kOneMicrosecond * 40;
    down_time_2 += kOneMicrosecond * 40;

    // Start a double-tap drag gesture.
    event = ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(down_time_1 + kOneMicrosecond,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY);
    gesture_provider_->OnTouchEvent(event);
    event = ObtainMotionEvent(
        down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 100);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));

    // Double tap zoom updates should not be sent.
    // Instead, the second tap drag becomes a scroll gesture sequence.
    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY + 200);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));
    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
        MotionEvent::ACTION_UP,
        kFakeCoordX,
        kFakeCoordY + 200);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_END));
}

// Verify that pinch zoom sends the proper event sequence.
TEST_F(GestureProviderTest, PinchZoom)
{
    base::TimeTicks event_time = base::TimeTicks::Now();
    const float touch_slop = GetTouchSlop();
    const float raw_offset_x = 3.2f;
    const float raw_offset_y = 4.3f;
    int motion_event_id = 6;

    gesture_provider_->SetDoubleTapSupportForPageEnabled(false);
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
    gesture_provider_->SetMultiTouchZoomSupportEnabled(true);

    int secondary_coord_x = kFakeCoordX + 20 * touch_slop;
    int secondary_coord_y = kFakeCoordY + 20 * touch_slop;

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    event.SetPrimaryPointerId(motion_event_id);
    event.SetRawOffset(raw_offset_x, raw_offset_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(kFakeCoordX, GetMostRecentGestureEvent().x);
    EXPECT_EQ(kFakeCoordY, GetMostRecentGestureEvent().y);
    EXPECT_EQ(kFakeCoordX + raw_offset_x, GetMostRecentGestureEvent().raw_x);
    EXPECT_EQ(kFakeCoordY + raw_offset_y, GetMostRecentGestureEvent().raw_y);
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
        GetMostRecentGestureEvent().details.bounding_box_f());

    // Toggling double-tap support should not take effect until the next sequence.
    gesture_provider_->SetDoubleTapSupportForPageEnabled(true);

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_POINTER_DOWN,
        kFakeCoordX,
        kFakeCoordY,
        secondary_coord_x,
        secondary_coord_y);
    event.SetPrimaryPointerId(motion_event_id);
    event.SetRawOffset(raw_offset_x, raw_offset_y);

    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(1U, GetReceivedGestureCount());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
        GetMostRecentGestureEvent().details.bounding_box_f());

    secondary_coord_x += 5 * touch_slop;
    secondary_coord_y += 5 * touch_slop;
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY,
        secondary_coord_x,
        secondary_coord_y);
    event.SetPrimaryPointerId(motion_event_id);
    event.SetRawOffset(raw_offset_x, raw_offset_y);

    // Toggling double-tap support should not take effect until the next sequence.
    gesture_provider_->SetDoubleTapSupportForPageEnabled(false);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE));

    EXPECT_EQ((kFakeCoordX + secondary_coord_x) / 2, GetReceivedGesture(3).x);
    EXPECT_EQ((kFakeCoordY + secondary_coord_y) / 2, GetReceivedGesture(3).y);
    EXPECT_EQ((kFakeCoordX + secondary_coord_x) / 2 + raw_offset_x,
        GetReceivedGesture(3).raw_x);
    EXPECT_EQ((kFakeCoordY + secondary_coord_y) / 2 + raw_offset_y,
        GetReceivedGesture(3).raw_y);

    EXPECT_EQ(
        gfx::RectF(kFakeCoordX - kMockTouchRadius, kFakeCoordY - kMockTouchRadius,
            secondary_coord_x - kFakeCoordX + kMockTouchRadius * 2,
            secondary_coord_y - kFakeCoordY + kMockTouchRadius * 2),
        GetMostRecentGestureEvent().details.bounding_box_f());

    secondary_coord_x += 2 * touch_slop;
    secondary_coord_y += 2 * touch_slop;
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY,
        secondary_coord_x,
        secondary_coord_y);
    event.SetPrimaryPointerId(motion_event_id);

    // Toggling double-tap support should not take effect until the next sequence.
    gesture_provider_->SetDoubleTapSupportForPageEnabled(true);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE));
    EXPECT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_LT(1.f, GetMostRecentGestureEvent().details.scale());
    EXPECT_EQ(
        gfx::RectF(kFakeCoordX - kMockTouchRadius, kFakeCoordY - kMockTouchRadius,
            secondary_coord_x - kFakeCoordX + kMockTouchRadius * 2,
            secondary_coord_y - kFakeCoordY + kMockTouchRadius * 2),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_POINTER_UP,
        kFakeCoordX,
        kFakeCoordY,
        secondary_coord_x,
        secondary_coord_y);
    event.SetPrimaryPointerId(motion_event_id);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(ET_GESTURE_PINCH_END, GetMostRecentGestureEventType());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_END));
    EXPECT_EQ(
        gfx::RectF(kFakeCoordX - kMockTouchRadius, kFakeCoordY - kMockTouchRadius,
            secondary_coord_x - kFakeCoordX + kMockTouchRadius * 2,
            secondary_coord_y - kFakeCoordY + kMockTouchRadius * 2),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(
        gfx::RectF(kFakeCoordX - kMockTouchRadius, kFakeCoordY - kMockTouchRadius,
            kMockTouchRadius * 2, kMockTouchRadius * 2),
        GetMostRecentGestureEvent().details.bounding_box_f());
}

// Verify that no accidental pinching occurs if the touch size is large relative
// to the min scaling span when the touch major value is used in scaling.
TEST_F(GestureProviderTest, NoPinchZoomWithFatFinger)
{
    base::TimeTicks event_time = base::TimeTicks::Now();
    const float kFatFingerSize = GetMinScalingSpan() * 3.f;

    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
    gesture_provider_->SetMultiTouchZoomSupportEnabled(true);

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1U, GetReceivedGestureCount());

    event = ObtainMotionEvent(event_time + kOneSecond,
        MotionEvent::ACTION_MOVE);
    event.SetTouchMajor(0.1f);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(1U, GetReceivedGestureCount());

    event = ObtainMotionEvent(event_time + kOneSecond * 2,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX + 1.f,
        kFakeCoordY);
    event.SetTouchMajor(1.f);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(1U, GetReceivedGestureCount());

    event = ObtainMotionEvent(event_time + kOneSecond * 3,
        MotionEvent::ACTION_MOVE);
    event.SetTouchMajor(kFatFingerSize * 3.5f);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(1U, GetReceivedGestureCount());

    event = ObtainMotionEvent(event_time + kOneSecond * 4,
        MotionEvent::ACTION_MOVE);
    event.SetTouchMajor(kFatFingerSize * 5.f);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(1U, GetReceivedGestureCount());

    event = ObtainMotionEvent(event_time + kOneSecond * 4,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX + 50.f,
        kFakeCoordY - 25.f);
    event.SetTouchMajor(kFatFingerSize * 10.f);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));

    event = ObtainMotionEvent(event_time + kOneSecond * 4,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX + 100.f,
        kFakeCoordY - 50.f);
    event.SetTouchMajor(kFatFingerSize * 5.f);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
}

// Verify that multi-finger swipe sends the proper event sequence.
TEST_F(GestureProviderTest, MultiFingerSwipe)
{
    EnableSwipe();
    gesture_provider_->SetMultiTouchZoomSupportEnabled(false);
    const float min_swipe_velocity = GetMinSwipeVelocity();

    // One finger - swipe right
    OneFingerSwipe(2 * min_swipe_velocity, 0);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
    EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_right());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    ResetGestureDetection();

    // One finger - swipe left
    OneFingerSwipe(-2 * min_swipe_velocity, 0);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
    EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_left());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    ResetGestureDetection();

    // One finger - swipe down
    OneFingerSwipe(0, 2 * min_swipe_velocity);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
    EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_down());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    ResetGestureDetection();

    // One finger - swipe up
    OneFingerSwipe(0, -2 * min_swipe_velocity);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
    EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_up());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    ResetGestureDetection();

    // Two fingers
    // Swipe right.
    TwoFingerSwipe(min_swipe_velocity * 2, 0, min_swipe_velocity * 2, 0);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
    EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_right());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    ResetGestureDetection();

    // Swipe left.
    TwoFingerSwipe(-min_swipe_velocity * 2, 0, -min_swipe_velocity * 2, 0);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
    EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_left());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    ResetGestureDetection();

    // No swipe with different touch directions.
    TwoFingerSwipe(min_swipe_velocity * 2, 0, -min_swipe_velocity * 2, 0);
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE));
    ResetGestureDetection();

    // No swipe without a dominant direction.
    TwoFingerSwipe(min_swipe_velocity * 2,
        min_swipe_velocity * 2,
        min_swipe_velocity * 2,
        min_swipe_velocity * 2);
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE));
    ResetGestureDetection();

    // Swipe down with non-zero velocities on both axes and dominant direction.
    TwoFingerSwipe(-min_swipe_velocity,
        min_swipe_velocity * 4,
        -min_swipe_velocity,
        min_swipe_velocity * 4);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
    EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_down());
    EXPECT_FALSE(GetMostRecentGestureEvent().details.swipe_left());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    ResetGestureDetection();

    // Swipe up with non-zero velocities on both axes.
    TwoFingerSwipe(min_swipe_velocity,
        -min_swipe_velocity * 4,
        min_swipe_velocity,
        -min_swipe_velocity * 4);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
    EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_up());
    EXPECT_FALSE(GetMostRecentGestureEvent().details.swipe_right());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    ResetGestureDetection();

    // No swipe without sufficient velocity.
    TwoFingerSwipe(min_swipe_velocity / 2, 0, 0, 0);
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE));
    ResetGestureDetection();

    // Swipe up with one small and one medium velocity in slightly different but
    // not opposing directions.
    TwoFingerSwipe(min_swipe_velocity / 2,
        min_swipe_velocity / 2,
        0,
        min_swipe_velocity * 2);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
    EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_down());
    EXPECT_FALSE(GetMostRecentGestureEvent().details.swipe_right());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    ResetGestureDetection();

    // No swipe in orthogonal directions.
    TwoFingerSwipe(min_swipe_velocity * 2, 0, 0, min_swipe_velocity * 7);
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE));
    ResetGestureDetection();

    // Three finger swipe in same directions.
    ThreeFingerSwipe(min_swipe_velocity * 2,
        0,
        min_swipe_velocity * 3,
        0,
        min_swipe_velocity * 4,
        0);
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
    EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_right());
    EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
    ResetGestureDetection();

    // No three finger swipe in different directions.
    ThreeFingerSwipe(min_swipe_velocity * 2,
        0,
        0,
        min_swipe_velocity * 3,
        min_swipe_velocity * 4,
        0);
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE));
}

// Verify that the timer of LONG_PRESS will be cancelled when scrolling begins
// so LONG_PRESS and LONG_TAP won't be triggered.
TEST_F(GestureProviderTest, GesturesCancelledAfterLongPressCausesLostFocus)
{
    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    const base::TimeDelta long_press_timeout = GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
    RunTasksAndWait(long_press_timeout);
    EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    EXPECT_TRUE(CancelActiveTouchSequence());
    EXPECT_FALSE(HasDownEvent());

    event = ObtainMotionEvent(event_time + long_press_timeout,
        MotionEvent::ACTION_UP);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP));
}

// Verify that inserting a touch cancel event will trigger proper touch and
// gesture sequence cancellation.
TEST_F(GestureProviderTest, CancelActiveTouchSequence)
{
    base::TimeTicks event_time = base::TimeTicks::Now();
    int motion_event_id = 6;

    EXPECT_FALSE(CancelActiveTouchSequence());
    EXPECT_EQ(0U, GetReceivedGestureCount());

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    event.SetPrimaryPointerId(motion_event_id);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    ASSERT_TRUE(CancelActiveTouchSequence());
    EXPECT_FALSE(HasDownEvent());

    // Subsequent MotionEvent's are dropped until ACTION_DOWN.
    event = ObtainMotionEvent(event_time + kOneMicrosecond,
        MotionEvent::ACTION_MOVE);
    EXPECT_FALSE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
        MotionEvent::ACTION_UP);
    EXPECT_FALSE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time + kOneMicrosecond * 3,
        MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
}

TEST_F(GestureProviderTest, DoubleTapDragZoomCancelledOnSecondaryPointerDown)
{
    const base::TimeTicks down_time_1 = TimeTicks::Now();
    const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay();

    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);

    MockMotionEvent event = ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(down_time_1 + kOneMicrosecond, MotionEvent::ACTION_UP);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(down_time_2, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY - 30);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
    EXPECT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
        MotionEvent::ACTION_POINTER_DOWN,
        kFakeCoordX,
        kFakeCoordY - 30,
        kFakeCoordX + 50,
        kFakeCoordY + 50);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_PINCH_END, GetMostRecentGestureEventType());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());

    const size_t gesture_count = GetReceivedGestureCount();
    event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
        MotionEvent::ACTION_POINTER_UP,
        kFakeCoordX,
        kFakeCoordY - 30,
        kFakeCoordX + 50,
        kFakeCoordY + 50);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(gesture_count, GetReceivedGestureCount());

    event = ObtainMotionEvent(down_time_2 + kOneSecond,
        MotionEvent::ACTION_UP);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(gesture_count + 1, GetReceivedGestureCount());
    EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
}

// Verify that gesture begin and gesture end events are dispatched correctly.
TEST_F(GestureProviderTest, GestureBeginAndEnd)
{
    EnableBeginEndTypes();
    base::TimeTicks event_time = base::TimeTicks::Now();
    const float raw_offset_x = 7.5f;
    const float raw_offset_y = 5.7f;

    EXPECT_EQ(0U, GetReceivedGestureCount());
    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 1, 1);
    event.SetRawOffset(raw_offset_x, raw_offset_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_BEGIN, GetReceivedGesture(0).type());
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(2U, GetReceivedGestureCount());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(1, GetMostRecentGestureEvent().x);
    EXPECT_EQ(1, GetMostRecentGestureEvent().y);
    EXPECT_EQ(1 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
    EXPECT_EQ(1 + raw_offset_y, GetMostRecentGestureEvent().raw_y);
    EXPECT_EQ(gfx::RectF(1 - kMockTouchRadius, 1 - kMockTouchRadius,
                  kMockTouchRadius * 2, kMockTouchRadius * 2),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2);
    event.SetRawOffset(raw_offset_x, raw_offset_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType());
    EXPECT_EQ(3U, GetReceivedGestureCount());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(2, GetMostRecentGestureEvent().x);
    EXPECT_EQ(2, GetMostRecentGestureEvent().y);
    EXPECT_EQ(2 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
    EXPECT_EQ(2 + raw_offset_y, GetMostRecentGestureEvent().raw_y);

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2, 3, 3);
    event.SetRawOffset(raw_offset_x, raw_offset_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType());
    EXPECT_EQ(4U, GetReceivedGestureCount());
    EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(3, GetMostRecentGestureEvent().x);
    EXPECT_EQ(3, GetMostRecentGestureEvent().y);
    EXPECT_EQ(3 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
    EXPECT_EQ(3 + raw_offset_y, GetMostRecentGestureEvent().raw_y);

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_POINTER_UP, 1, 1, 2, 2, 3, 3);
    event.SetRawOffset(raw_offset_x, raw_offset_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType());
    EXPECT_EQ(5U, GetReceivedGestureCount());
    EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(1, GetMostRecentGestureEvent().x);
    EXPECT_EQ(1, GetMostRecentGestureEvent().y);
    EXPECT_EQ(1 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
    EXPECT_EQ(1 + raw_offset_y, GetMostRecentGestureEvent().raw_y);

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_POINTER_DOWN, 2, 2, 3, 3, 4, 4);
    event.SetRawOffset(raw_offset_x, raw_offset_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType());
    EXPECT_EQ(6U, GetReceivedGestureCount());
    EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(4, GetMostRecentGestureEvent().x);
    EXPECT_EQ(4, GetMostRecentGestureEvent().y);
    EXPECT_EQ(4 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
    EXPECT_EQ(4 + raw_offset_y, GetMostRecentGestureEvent().raw_y);

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_POINTER_UP, 2, 2, 3, 3, 4, 4);
    event.SetRawOffset(raw_offset_x, raw_offset_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType());
    EXPECT_EQ(7U, GetReceivedGestureCount());
    EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(2, GetMostRecentGestureEvent().x);
    EXPECT_EQ(2, GetMostRecentGestureEvent().y);
    EXPECT_EQ(2 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
    EXPECT_EQ(2 + raw_offset_y, GetMostRecentGestureEvent().raw_y);

    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_POINTER_UP, 3, 3, 4, 4);
    event.SetRawOffset(raw_offset_x, raw_offset_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType());
    EXPECT_EQ(8U, GetReceivedGestureCount());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(3, GetMostRecentGestureEvent().x);
    EXPECT_EQ(3, GetMostRecentGestureEvent().y);
    EXPECT_EQ(3 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
    EXPECT_EQ(3 + raw_offset_y, GetMostRecentGestureEvent().raw_y);

    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP, 4, 4);
    event.SetRawOffset(raw_offset_x, raw_offset_y);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType());
    EXPECT_EQ(9U, GetReceivedGestureCount());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(4, GetMostRecentGestureEvent().x);
    EXPECT_EQ(4, GetMostRecentGestureEvent().y);
    EXPECT_EQ(4 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
    EXPECT_EQ(4 + raw_offset_y, GetMostRecentGestureEvent().raw_y);
}

// Verify that gesture begin and gesture end events are dispatched correctly
// when an ACTION_CANCEL is received.
TEST_F(GestureProviderTest, GestureBeginAndEndOnCancel)
{
    EnableBeginEndTypes();
    base::TimeTicks event_time = base::TimeTicks::Now();

    EXPECT_EQ(0U, GetReceivedGestureCount());
    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 1, 1);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_BEGIN, GetReceivedGesture(0).type());
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(2U, GetReceivedGestureCount());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(gfx::RectF(1 - kMockTouchRadius, 1 - kMockTouchRadius,
                  kMockTouchRadius * 2, kMockTouchRadius * 2),
        GetMostRecentGestureEvent().details.bounding_box_f());
    EXPECT_EQ(1, GetMostRecentGestureEvent().x);
    EXPECT_EQ(1, GetMostRecentGestureEvent().y);

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType());
    EXPECT_EQ(3U, GetReceivedGestureCount());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(2, GetMostRecentGestureEvent().x);
    EXPECT_EQ(2, GetMostRecentGestureEvent().y);

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2, 3, 3);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType());
    EXPECT_EQ(4U, GetReceivedGestureCount());
    EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(3, GetMostRecentGestureEvent().x);
    EXPECT_EQ(3, GetMostRecentGestureEvent().y);

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_CANCEL, 1, 1, 2, 2, 3, 3);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(5U, GetReceivedGestureCount());
    EXPECT_EQ(3, GetReceivedGesture(4).details.touch_points());
    EXPECT_EQ(ET_GESTURE_END, GetReceivedGesture(4).type());
    EXPECT_EQ(1, GetMostRecentGestureEvent().x);
    EXPECT_EQ(1, GetMostRecentGestureEvent().y);

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_CANCEL, 1, 1, 3, 3);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(6U, GetReceivedGestureCount());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEvent().type());
    EXPECT_EQ(1, GetMostRecentGestureEvent().x);
    EXPECT_EQ(1, GetMostRecentGestureEvent().y);

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_CANCEL, 3, 3);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEvent().type());
    EXPECT_EQ(3, GetMostRecentGestureEvent().x);
    EXPECT_EQ(3, GetMostRecentGestureEvent().y);
}

// Test a simple two finger tap
TEST_F(GestureProviderTest, TwoFingerTap)
{
    // The time between ACTION_POINTER_DOWN and ACTION_POINTER_UP must be <= the
    // two finger tap delay.
    EnableTwoFingerTap(kMaxTwoFingerTapSeparation, base::TimeDelta());
    const float scaled_touch_slop = GetTouchSlop();

    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 0, 0);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE,
        0,
        scaled_touch_slop / 2);

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_POINTER_DOWN,
        0,
        0,
        kMaxTwoFingerTapSeparation / 2,
        0);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE,
        0,
        -scaled_touch_slop / 2,
        kMaxTwoFingerTapSeparation / 2 + scaled_touch_slop / 2,
        0);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_POINTER_UP,
        0,
        0,
        kMaxTwoFingerTapSeparation,
        0);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type());
    EXPECT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type());
    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetReceivedGesture(2).type());
    EXPECT_EQ(ET_GESTURE_TWO_FINGER_TAP, GetReceivedGesture(3).type());
    EXPECT_EQ(4U, GetReceivedGestureCount());

    EXPECT_EQ(kMockTouchRadius * 2,
        GetReceivedGesture(3).details.first_finger_width());
    EXPECT_EQ(kMockTouchRadius * 2,
        GetReceivedGesture(3).details.first_finger_height());
}

// Test preventing a two finger tap via finger movement.
TEST_F(GestureProviderTest, TwoFingerTapCancelledByFingerMovement)
{
    EnableTwoFingerTap(kMaxTwoFingerTapSeparation, base::TimeDelta());
    const float scaled_touch_slop = GetTouchSlop();
    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_POINTER_DOWN,
        kFakeCoordX,
        kFakeCoordY,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY,
        kFakeCoordX + scaled_touch_slop + 0.1,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_POINTER_UP,
        kFakeCoordX,
        kFakeCoordY,
        kFakeCoordX,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type());
    EXPECT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type());
    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetReceivedGesture(2).type());
    EXPECT_FLOAT_EQ((scaled_touch_slop + 0.1) / 2,
        GetReceivedGesture(2).details.scroll_x());
    EXPECT_EQ(0, GetReceivedGesture(2).details.scroll_y());
    EXPECT_EQ(3U, GetReceivedGestureCount());
}

// Test preventing a two finger tap by waiting too long before releasing the
// secondary pointer.
TEST_F(GestureProviderTest, TwoFingerTapCancelledByDelay)
{
    base::TimeDelta two_finger_tap_timeout = kOneSecond;
    EnableTwoFingerTap(kMaxTwoFingerTapSeparation, two_finger_tap_timeout);
    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY);

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_POINTER_DOWN,
        kFakeCoordX,
        kFakeCoordY,
        kFakeCoordX + kMaxTwoFingerTapSeparation / 2,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time + kOneSecond + kOneMicrosecond,
        MotionEvent::ACTION_POINTER_UP,
        kFakeCoordX,
        kFakeCoordY,
        kFakeCoordX + kMaxTwoFingerTapSeparation / 2,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type());
    EXPECT_EQ(1U, GetReceivedGestureCount());
}

// Test preventing a two finger tap by pressing the secondary pointer too far
// from the first
TEST_F(GestureProviderTest, TwoFingerTapCancelledByDistanceBetweenPointers)
{
    EnableTwoFingerTap(kMaxTwoFingerTapSeparation, base::TimeDelta());
    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_POINTER_DOWN,
        kFakeCoordX,
        kFakeCoordY,
        kFakeCoordX + kMaxTwoFingerTapSeparation,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_POINTER_UP,
        kFakeCoordX,
        kFakeCoordY,
        kFakeCoordX + kMaxTwoFingerTapSeparation,
        kFakeCoordY);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type());
    EXPECT_EQ(1U, GetReceivedGestureCount());
}

// Verify that pinch zoom only sends updates which exceed the
// min_pinch_update_span_delta.
TEST_F(GestureProviderTest, PinchZoomWithThreshold)
{
    const float kMinPinchUpdateDistance = 5;

    base::TimeTicks event_time = base::TimeTicks::Now();
    const float touch_slop = GetTouchSlop();

    SetMinPinchUpdateSpanDelta(kMinPinchUpdateDistance);
    gesture_provider_->SetDoubleTapSupportForPageEnabled(false);
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
    gesture_provider_->SetMultiTouchZoomSupportEnabled(true);

    int secondary_coord_x = kFakeCoordX + 20 * touch_slop;
    int secondary_coord_y = kFakeCoordY + 20 * touch_slop;

    // First finger down.
    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    // Second finger down.
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_POINTER_DOWN,
        kFakeCoordX,
        kFakeCoordY,
        secondary_coord_x,
        secondary_coord_y);

    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(1U, GetReceivedGestureCount());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());

    // Move second finger.
    secondary_coord_x += 5 * touch_slop;
    secondary_coord_y += 5 * touch_slop;
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY,
        secondary_coord_x,
        secondary_coord_y);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE));

    // Small move, shouldn't trigger pinch.
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY,
        secondary_coord_x + kMinPinchUpdateDistance,
        secondary_coord_y);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());

    // Small move, but combined with the previous move, should trigger pinch. We
    // need to overshoot kMinPinchUpdateDistance by a fair bit, as the span
    // calculation factors in touch radius.
    const float kOvershootMinPinchUpdateDistance = 3;
    event = ObtainMotionEvent(event_time,
        MotionEvent::ACTION_MOVE,
        kFakeCoordX,
        kFakeCoordY,
        secondary_coord_x + kMinPinchUpdateDistance + kOvershootMinPinchUpdateDistance,
        secondary_coord_y);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
}

// Verify that the min gesture bound setting is honored.
TEST_F(GestureProviderTest, MinGestureBoundsLength)
{
    const float kMinGestureBoundsLength = 10.f * kMockTouchRadius;
    SetMinMaxGestureBoundsLength(kMinGestureBoundsLength, 0.f);
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);

    base::TimeTicks event_time = base::TimeTicks::Now();
    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(kMinGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().width());
    EXPECT_EQ(kMinGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().height());

    event = ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(kMinGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().width());
    EXPECT_EQ(kMinGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().height());
}

TEST_F(GestureProviderTest, MaxGestureBoundsLength)
{
    const float kMaxGestureBoundsLength = kMockTouchRadius / 10.f;
    SetMinMaxGestureBoundsLength(0.f, kMaxGestureBoundsLength);
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);

    base::TimeTicks event_time = base::TimeTicks::Now();
    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(kMaxGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().width());
    EXPECT_EQ(kMaxGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().height());

    event = ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(kMaxGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().width());
    EXPECT_EQ(kMaxGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().height());
}

TEST_F(GestureProviderTest, ZeroRadiusBoundingBox)
{
    base::TimeTicks event_time = base::TimeTicks::Now();
    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 10, 20);
    event.SetTouchMajor(0);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(gfx::RectF(10, 20, 0, 0),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_POINTER_DOWN, 10, 20, 110, 120);
    event.SetTouchMajor(0);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    event = ObtainMotionEvent(
        event_time, MotionEvent::ACTION_MOVE, 10, 20, 110, 150);
    event.SetTouchMajor(0);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(gfx::RectF(10, 20, 100, 130),
        GetMostRecentGestureEvent().details.bounding_box_f());
}

// Verify that the min/max gesture bound settings are not applied to stylus
// or mouse-derived MotionEvents.
TEST_F(GestureProviderTest, NoMinOrMaxGestureBoundsLengthWithStylusOrMouse)
{
    const float kMinGestureBoundsLength = 5.f * kMockTouchRadius;
    const float kMaxGestureBoundsLength = 10.f * kMockTouchRadius;
    SetMinMaxGestureBoundsLength(kMinGestureBoundsLength,
        kMaxGestureBoundsLength);
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);

    base::TimeTicks event_time = base::TimeTicks::Now();
    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    event.SetTouchMajor(0);
    event.SetToolType(0, MotionEvent::TOOL_TYPE_MOUSE);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));

    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(MotionEvent::TOOL_TYPE_MOUSE,
        GetMostRecentGestureEvent().primary_tool_type);
    EXPECT_EQ(0.f, GetMostRecentGestureEvent().details.bounding_box_f().width());
    EXPECT_EQ(0.f, GetMostRecentGestureEvent().details.bounding_box_f().height());

    event = ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
    event.SetTouchMajor(1);
    event.SetToolType(0, MotionEvent::TOOL_TYPE_STYLUS);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(MotionEvent::TOOL_TYPE_STYLUS,
        GetMostRecentGestureEvent().primary_tool_type);
    EXPECT_EQ(0, GetMostRecentGestureEvent().details.bounding_box_f().width());
    EXPECT_EQ(0, GetMostRecentGestureEvent().details.bounding_box_f().height());

    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    event.SetTouchMajor(2.f * kMaxGestureBoundsLength);
    event.SetToolType(0, MotionEvent::TOOL_TYPE_MOUSE);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(MotionEvent::TOOL_TYPE_MOUSE,
        GetMostRecentGestureEvent().primary_tool_type);
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(2.f * kMaxGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().width());
    EXPECT_EQ(2.f * kMaxGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().height());

    event = ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
    event.SetTouchMajor(2.f * kMaxGestureBoundsLength);
    event.SetToolType(0, MotionEvent::TOOL_TYPE_ERASER);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(MotionEvent::TOOL_TYPE_ERASER,
        GetMostRecentGestureEvent().primary_tool_type);
    EXPECT_EQ(2.f * kMaxGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().width());
    EXPECT_EQ(2.f * kMaxGestureBoundsLength,
        GetMostRecentGestureEvent().details.bounding_box_f().height());
}

// Test the bounding box for show press and tap gestures.
TEST_F(GestureProviderTest, BoundingBoxForShowPressAndTapGesture)
{
    base::TimeTicks event_time = base::TimeTicks::Now();
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
    base::TimeDelta showpress_timeout = kOneMicrosecond;
    base::TimeDelta longpress_timeout = kOneSecond;
    SetShowPressAndLongPressTimeout(showpress_timeout, longpress_timeout);

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 10, 10);
    event.SetTouchMajor(10);

    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(gfx::RectF(5, 5, 10, 10),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(
        event_time + kOneMicrosecond, MotionEvent::ACTION_MOVE, 11, 9);
    event.SetTouchMajor(20);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(
        event_time + kOneMicrosecond, MotionEvent::ACTION_MOVE, 8, 11);
    event.SetTouchMajor(10);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    RunTasksAndWait(showpress_timeout + kOneMicrosecond);
    EXPECT_EQ(ET_GESTURE_SHOW_PRESS, GetMostRecentGestureEventType());
    EXPECT_EQ(gfx::RectF(0, 0, 20, 20),
        GetMostRecentGestureEvent().details.bounding_box_f());

    event = ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
    event.SetTouchMajor(30);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());

    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
    EXPECT_EQ(gfx::RectF(0, 0, 20, 20),
        GetMostRecentGestureEvent().details.bounding_box_f());
}

TEST_F(GestureProviderTest, SingleTapRepeat)
{
    SetSingleTapRepeatInterval(3);

    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);

    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());

    // A second tap after the double-tap timeout window will not increment
    // the tap count.
    event_time += GetDoubleTapTimeout() + kOneMicrosecond;
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());

    // A secondary tap within the tap repeat period should increment
    // the tap count.
    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(2, GetMostRecentGestureEvent().details.tap_count());

    // A secondary tap within the tap repeat location threshold should increment
    // the tap count.
    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, kFakeCoordX,
        kFakeCoordY + GetTouchSlop() / 2);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(3, GetMostRecentGestureEvent().details.tap_count());

    // The tap count should reset after hitting the repeat length.
    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());

    // If double-tap is enabled, the tap repeat count should always be 1.
    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    RunTasksAndWait(GetDoubleTapTimeout());
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());

    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    RunTasksAndWait(GetDoubleTapTimeout());
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());
}

TEST_F(GestureProviderTest, SingleTapRepeatLengthOfOne)
{
    SetSingleTapRepeatInterval(1);

    gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);

    base::TimeTicks event_time = base::TimeTicks::Now();

    MockMotionEvent event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    gesture_provider_->OnTouchEvent(event);
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());

    // Repeated taps should still produce a tap count of 1 if the
    // tap repeat length is 1.
    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());

    event_time += GetValidDoubleTapDelay();
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, kFakeCoordX,
        kFakeCoordY + GetTouchSlop() / 2);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
    EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
    EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());
}

} // namespace ui
